package cc.yaomm.common.basic.base;

import cc.yaomm.common.basic.lang.po.BaseParamPO;
import cc.yaomm.common.basic.util.BeanUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.reflection.property.PropertyNamer;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Created by XQL on 2020/7/11.
 * 扩展{@link com.baomidou.mybatisplus.core.mapper.BaseMapper}功能
 */
public interface SuperMapper<T> extends BaseMapper<T> {

    Logger LOGGER = LoggerFactory.getLogger(SuperMapper.class);

    Log log = LogFactory.getLog(SuperMapper.class);

    /**
     * 批量大小
     */
    int BATCH_SIZE = 1024;

    /**
     * <p>@Description : 获取实体Class</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2020/8/13  </p>
     */
    default Class<?> getEntityClass() {
        //获取当前Mapper的接口class
        Class<?> mapperClass = (Class<?>) this.getClass().getGenericInterfaces()[0];
        //获取Mapper的父级接口class
        ParameterizedType superMapperClass = (ParameterizedType) mapperClass.getGenericInterfaces()[0];
        //获取泛型class
        return (Class<?>) superMapperClass.getActualTypeArguments()[0];
    }

    /**
     * <p>@Description : 批量插入</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:15 </p>
     *
     * @param entityList 实体集合
     * @batchSize 更新批次数量（每次更新最大数量）
     */
    default boolean batchInsert(Collection<T> entityList, int batchSize) {
        String sqlStatement = SqlHelper.getSqlStatement(this.getClass(), SqlMethod.INSERT_ONE);
        return SqlHelper.executeBatch(this.getEntityClass(), this.log, entityList, batchSize,
                (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }

    /**
     * <p>@Description : 批量插入</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:15 </p>
     *
     * @param entityList 实体集合
     */
    default boolean batchInsert(Collection<T> entityList) {
        return this.batchInsert(entityList, BATCH_SIZE);
    }

    /**
     * <p>@Description : 根据@TableId判断是更新还是新增记录</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:36 </p>
     *
     * @param entity 实体
     * @return
     */
    default boolean insertOrUpdate(T entity) {
        if (entity == null) {
            return false;
        }

        Class<?> cls = entity.getClass();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
        Assert.notNull(tableInfo, "实体类上没有找到@TableName注解!");
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "实体类中没有找到@TableId注解的属性!");
        Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
        return SqlHelper.retBool(StringUtils.checkValNull(idVal) || Objects.isNull(this.get((Serializable) idVal)) ? this.insert(entity) : this.updateById(entity));
    }

    /**
     * <p>@Description : 批量更新插入</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:16 </p>
     *
     * @param entityList 实体集合
     * @return
     */
    default boolean batchInsertOrUpdate(Collection<T> entityList) {
        if (CollectionUtils.isEmpty(entityList)) {
            return true;
        }

        Class<?> clazz = this.getEntityClass();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
        Assert.notNull(tableInfo, "实体类中没有找到到TableInfo信息!");

        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "实体类中没有找到@TableId注解的属性!");

        return SqlHelper.saveOrUpdateBatch(clazz, this.getClass(), this.log, entityList, BATCH_SIZE, (sqlSession, entity) -> {
            Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
            return StringUtils.checkValNull(idVal)
                    || CollectionUtils.isEmpty(sqlSession.selectList(
                    SqlHelper.getSqlStatement(this.getClass(), SqlMethod.SELECT_BY_ID), entity));
        }, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(SqlHelper.getSqlStatement(this.getClass(), SqlMethod.UPDATE_BY_ID), param);
        });
    }

    /**
     * <p>@Description : 根据ID批量更新</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:34 </p>
     *
     * @param entityList 实体集合
     * @param batchSize  更新批次数量（每次更新最大数量）
     * @return
     */
    default boolean batchUpdateById(Collection<T> entityList, int batchSize) {
        Class<?> clazz = this.getEntityClass();
        String sqlStatement = SqlHelper.getSqlStatement(this.getClass(), SqlMethod.UPDATE_BY_ID);
        return SqlHelper.executeBatch(clazz, this.log, entityList, batchSize, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
    }

    default boolean batchUpdateById(Collection<T> entityList) {
        return this.batchUpdateById(entityList, BATCH_SIZE);
    }

    /**
     * <p>@Description : 根据实体包装类的条件更新记录</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:29 </p>
     *
     * @param entity    实体(更新的内容）
     * @param condition 更新条件 {@link com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper}
     * @return
     */
    default boolean update(T entity, T condition) {
        UpdateWrapper<T> wrapper = new UpdateWrapper<>();
        wrapper.setEntity(condition);
        return SqlHelper.retBool(this.update(entity, wrapper));
    }

    /**
     * <p>@Description : 根据实体条件删除数据</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2020/8/14  </p>
     */
    default boolean delete(T condition) {
        UpdateWrapper<T> wrapper = new UpdateWrapper<>();
        wrapper.setEntity(condition);
        return SqlHelper.retBool(this.delete(wrapper));
    }

    /**
     * <p>@Description : 根据ID查询对象</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:40 </p>
     *
     * @param id 主键
     * @return
     */
    default T get(Serializable id) {
        return this.selectById(id);
    }

    /**
     * <p>@Description : 根据实体类的条件，获取第一条数据</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 16:17 </p>
     *
     * @param condition 实体 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
     * @return
     */
    default T get(T condition) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        wrapper.setEntity(condition);
        List<T> result = this.selectList(wrapper);
        if (CollectionUtils.isEmpty(result)) {
            return null;
        }
        return result.get(0);

    }

    /**
     * <p>@Description : 根据实体包装类的条件，获取第一条数据，响应自定义对象</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 16:19 </p>
     *
     * @param wrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
     * @param mapper  自定义对象
     * @param <R>
     * @return
     */
    default <R> R get(Wrapper<T> wrapper, Function<? super T, R> mapper) {
        List<R> list = this.find(wrapper, mapper);
        if (!CollectionUtils.isEmpty(list)) {
            int size = list.size();
            if (size > 1) {
                LOGGER.warn(String.format("Warn: execute Method There are  %s results.", size));
            }

            return list.get(0);
        } else {
            return null;
        }
    }

    default Map<String, Object> get(Wrapper<T> queryWrapper) {
        List<Map<String, Object>> result = this.selectMaps(queryWrapper);
        if (CollectionUtils.isEmpty(result)) {
            return null;
        }
        return result.get(0);
    }

    /**
     * <p>@Description : 根据ID集合批量查询</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2020/8/12  </p>
     */
    default List<T> findByIds(Collection<? extends Serializable> idList) {
        return this.selectBatchIds(idList);
    }

    /**
     * <p>@Description : 统计所有数据</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:44 </p>
     *
     * @return
     */
    default int count() {
        return SqlHelper.retCount(this.selectCount(Wrappers.emptyWrapper()));
    }

    /**
     * <p>@Description : 根据实体的条件，查询总条数</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:43 </p>
     *
     * @param condition 实体条件
     * @return
     */
    default int count(T condition) {
        LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
        wrapper.setEntity(condition);
        return SqlHelper.retCount(this.selectCount(wrapper));
    }

    /**
     * <p>@Description : 根据实体的条件，查询数据</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:45 </p>
     *
     * @param condition 实体条件
     * @return
     */
    default List<T> find(T condition) {
        if (condition == null) {
            return this.selectList(Wrappers.emptyWrapper());
        } else {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
            wrapper.setEntity(condition);
            return this.selectList(wrapper);
        }
    }

    /**
     * <p>@Description : 根据实体包装类的条件，查询数据，响应自定义对象</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 16:06 </p>
     *
     * @param wrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
     * @param mapper  自定义对象类
     * @param <R>
     * @return
     */
    default <R> List<R> find(Wrapper<T> wrapper, Function<? super T, R> mapper) {
        return this.selectList(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
    }

    /**
     * <p>@Description : 翻页查询</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:49 </p>
     *
     * @param condition  实体条件 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
     * @param pagination 分页模型
     * @return
     */
    default IPage<T> findByPage(T condition, IPage<T> pagination) {
        if (condition == null) {
            return this.selectPage(pagination, Wrappers.emptyWrapper());
        } else {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
            wrapper.setEntity(condition);
            return this.selectPage(pagination, wrapper);
        }
    }

    /**
     * <p>@Description : 翻页查询所有数据，自定响应数据对象</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 16:04 </p>
     *
     * @param page   翻页对象
     * @param mapper 自定义对象类
     * @param <R>
     * @return
     */
    default <R> IPage<R> findByPage(IPage<T> page, Function<? super T, R> mapper) {
        return this.selectPage(page, Wrappers.emptyWrapper()).convert(mapper);
    }

    /**
     * <p>@Description : 翻页查询，自定义响应数据对象</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 16:02 </p>
     *
     * @param page    翻页对象
     * @param wrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
     * @param mapper  自定义对象类
     * @param <R>
     * @return
     */
    default <R> IPage<R> findByPage(IPage<T> page, Wrapper<T> wrapper, Function<? super T, R> mapper) {
        return this.selectPage(page, wrapper).convert(mapper);
    }

    /**
     * <p>@Description : 翻页查询，返回List,注意：page参数需要设置isSearchCount=false</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/14 13:30 </p>
     *
     * @param condition 实体条件 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
     * @param pageNo    页码
     * @param pageSize  页大小
     * @return
     */
    default List<T> findByPageList(T condition, int pageNo, int pageSize) {
        IPage<T> page = new Page<>(pageNo, pageSize, false);
        if (condition == null) {
            page = this.selectPage(page, Wrappers.emptyWrapper());
        } else {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
            wrapper.setEntity(condition);
            page = this.selectPage(page, wrapper);
        }
        return page.getRecords();
    }

    /**
     * <p>@Description : 分页查询（支持实体条件的基础查询，复制查询不支持） </p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2021/6/28 16:54 </p>
     */
    default List<T> findByPage(BaseParamPO paramPO) {
        LambdaQueryWrapper<T> wrapper = paramPO.getWrapper();
        IPage<T> page = new Page<>(paramPO.getPage(), paramPO.getSize(), false);
        page = this.selectPage(page, wrapper);
        return page.getRecords();
    }

    /**
     * 分页查询对应的统计
     *
     * @param paramPO
     * @return
     */
    default Integer countByPage(BaseParamPO paramPO) {
        LambdaQueryWrapper<T> wrapper = paramPO.getWrapper();
        return this.selectCount(wrapper);
    }

    /**
     * <p>@Description : 翻页查询，返回List，自定义数据类型,注意：page需要设置isSearchCount=false</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/14 13:32 </p>
     *
     * @param page    翻页对象
     * @param wrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
     * @param mapper  自定义对象类
     * @param <R>
     * @return
     */
    default <R> List<R> findByPageList(IPage<T> page, Wrapper<T> wrapper, Function<? super T, R> mapper) {
        return this.findByPage(page, wrapper, mapper).getRecords();
    }

    /**
     * <p>@Description : 根据实体包装类的条件,返回查询后的第一列数据</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 15:57 </p>
     *
     * @param wrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
     * @param column  列名
     * @param <R>
     * @return
     */
    default <R> List<R> findOfObject(Wrapper<T> wrapper, Function<? super Object, R> column) {
        return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(column).collect(Collectors.toList());
    }

    /**
     * <p>@Description : 根据实体包装类的条件，将响应数据以对象的属性为Key的形式转成Map数据</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 16:22 </p>
     *
     * @param wrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper}
     * @param column  实体属性
     * @param <K>
     * @return
     */
    default <K> Map<K, T> findOfMap(Wrapper<T> wrapper, SFunction<T, K> column) {
        List<T> list = this.selectList(wrapper);
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyMap();
        }

        Map<K, T> map = new LinkedHashMap<>(list.size());
        for (T entity : list) {
            String fieldName = PropertyNamer.methodToProperty(LambdaUtils.resolve(column).getImplMethodName());
            Object fieldValue = ReflectionKit.getFieldValue(entity, fieldName);
            map.put((K) fieldValue, entity);
        }

        return map;
    }

    /**
     * <p>@Description : 查询所有数据，将响应数据以对象的属性为Key的形式转为Map数据</p>
     * <p>@Author : QiLin.Xing </p>
     * <p>@Date : 2019/2/12 16:25 </p>
     *
     * @param column 实体属性
     * @param <K>
     * @return
     */
    default <K> Map<K, T> findOfMap(SFunction<T, K> column) {
        return this.findOfMap(Wrappers.emptyWrapper(), column);
    }
}
